None
В вашем распоряжении данные о поведении клиентов, которые уже перешли на эти тарифы (из проекта курса «Статистический анализ данных»). Нужно построить модель для задачи классификации, которая выберет подходящий тариф. Предобработка данных не понадобится — вы её уже сделали.
Постройте модель с максимально большим значением accuracy. Чтобы сдать проект успешно, нужно довести долю правильных ответов по крайней мере до 0.75. Проверьте accuracy на тестовой выборке самостоятельно.
Описание данных
сalls — количество звонков,minutes — суммарная длительность звонков в минутах,messages — количество sms-сообщений,mb_used — израсходованный интернет-трафик в Мб,is_ultra — каким тарифом пользовался в течение месяца («Ультра» — 1, «Смарт» — 0).Цель проекта
Решить задачу бинарной классификации. Построить модель машинного обучения предсказывающую тарифный план пользователя по имеющимся признакам: количество звонков, суммарная длительность звонков в минутах, количество sms-сообщений, израсходованный интернет-трафик в Мб. Значение accuracy должно быть не менее 0.75.
Ход проекта
Данные о с информацией о клиентах получены из файла users_behavior.csv. Заранее известно, что данные предобработаны и готовы подаче на вход алгоритмам машинного обучения.
Таким образом, решение пройдёт в пять этапов:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.dummy import DummyClassifier
import optuna
from optuna.samplers import RandomSampler
from optuna.visualization.matplotlib import plot_param_importances
import warnings
warnings.filterwarnings('ignore')
data = pd.read_csv('users_behavior.csv')
data.head(15)
| calls | minutes | messages | mb_used | is_ultra | |
|---|---|---|---|---|---|
| 0 | 40.0 | 311.90 | 83.0 | 19915.42 | 0 |
| 1 | 85.0 | 516.75 | 56.0 | 22696.96 | 0 |
| 2 | 77.0 | 467.66 | 86.0 | 21060.45 | 0 |
| 3 | 106.0 | 745.53 | 81.0 | 8437.39 | 1 |
| 4 | 66.0 | 418.74 | 1.0 | 14502.75 | 0 |
| 5 | 58.0 | 344.56 | 21.0 | 15823.37 | 0 |
| 6 | 57.0 | 431.64 | 20.0 | 3738.90 | 1 |
| 7 | 15.0 | 132.40 | 6.0 | 21911.60 | 0 |
| 8 | 7.0 | 43.39 | 3.0 | 2538.67 | 1 |
| 9 | 90.0 | 665.41 | 38.0 | 17358.61 | 0 |
| 10 | 82.0 | 560.51 | 20.0 | 9619.53 | 1 |
| 11 | 45.0 | 344.32 | 13.0 | 19898.81 | 0 |
| 12 | 51.0 | 437.13 | 61.0 | 21523.58 | 0 |
| 13 | 56.0 | 433.07 | 16.0 | 16702.36 | 0 |
| 14 | 108.0 | 587.90 | 0.0 | 14406.50 | 1 |
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 3214 entries, 0 to 3213 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 calls 3214 non-null float64 1 minutes 3214 non-null float64 2 messages 3214 non-null float64 3 mb_used 3214 non-null float64 4 is_ultra 3214 non-null int64 dtypes: float64(4), int64(1) memory usage: 125.7 KB
data.hist(figsize=(15, 15));
data.describe()
| calls | minutes | messages | mb_used | is_ultra | |
|---|---|---|---|---|---|
| count | 3214.000000 | 3214.000000 | 3214.000000 | 3214.000000 | 3214.000000 |
| mean | 63.038892 | 438.208787 | 38.281269 | 17207.673836 | 0.306472 |
| std | 33.236368 | 234.569872 | 36.148326 | 7570.968246 | 0.461100 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 40.000000 | 274.575000 | 9.000000 | 12491.902500 | 0.000000 |
| 50% | 62.000000 | 430.600000 | 30.000000 | 16943.235000 | 0.000000 |
| 75% | 82.000000 | 571.927500 | 57.000000 | 21424.700000 | 1.000000 |
| max | 244.000000 | 1632.060000 | 224.000000 | 49745.730000 | 1.000000 |
data.duplicated().sum()
0
Вывод
В каждой строке таблицы — данные о расходах клиентов на связь. Обзор данных не выявил никаких проблем: пропуски отсуствуют, в столбцах используются корректные типы данных, дубликатов нет, графики параметров имеют вид распределения Пуассона, что говорит об отсуствии аномальных значений. Данные действительно не нуждаются в дополнительной обработке, и мы можем переходить к разбиению на выборки.
Для начала выделим матрицу признаков и целевую переменную из загруженных данных. В данном случае мы будем предсказывать значение в столбце is_ultra, поэтому исключим его из общего датафрейма и поместим его в отдельный Series.
features = data.drop(['is_ultra'], axis=1) # извлекаем признаки
target = data['is_ultra'] # извлекаем целевой признак
display(features.shape)
display(target.shape)
(3214, 4)
(3214,)
Отдельной тестовой выборки у нас нет, поэтому далее нужно разделить все данные на тренировочную, валидационную и тестовую выборку в соотношении 3:1:1.
features_train, features_valid_test, target_train, target_valid_test = train_test_split(
features,
target,
test_size=0.4,
random_state=12345) # отделим 40% данных
features_valid, features_test, target_valid, target_test = train_test_split(
features_valid_test,
target_valid_test,
test_size=0.5,
random_state=12345) # отведём по 20% от общего кол-ва данных на валидационную и тестовую выборки
print('Размер тренировчной выборки:', features_train.shape)
print('Размер валидационной выборки:', features_valid.shape)
print('Размер тестовой выборки:', features_test.shape)
Размер тренировчной выборки: (1928, 4) Размер валидационной выборки: (643, 4) Размер тестовой выборки: (643, 4)
Разбиение данных на выборки выполненно, можно переходить к обучению моделей.
Протестируем модель решающего дерева. Построим модель, не меняя геперпараметры.
model_tree = DecisionTreeClassifier(random_state=12345) # создаём модель
model_tree.fit(features_train, target_train) # обучаем модель
predictions = model_tree.predict(features_valid) # делаем предсказания целевого признака на валидационной выборке
accuracy = accuracy_score(target_valid, predictions) # считаем accuracy
accuracy
0.713841368584759
Уже имеем значение accuracy равное 0.714. Посмотрим, какого значения мы сможем добиться, меняя гиперпараметры.
Добавим перебор некоторых гиперпараметров модели для улучшения метрики accuracy.
%%capture optuna_output
def objective(trial):
depth = trial.suggest_int('max_depth', 1, 10)
criterion = trial.suggest_categorical('criterion', ['gini', 'entropy'])
splitter = trial.suggest_categorical('splitter', ['best', 'random'])
min_samples_split = trial.suggest_int('min_samples_split', 2, 10)
min_samples_leaf = trial.suggest_float('min_samples_leaf', 0.001, 0.5)
min_weight_fraction_leaf = trial.suggest_float('min_weight_fraction_leaf', 0, 0.5)
max_features = trial.suggest_categorical('max_features', ['sqrt', 'auto', 'log2'])
clf = DecisionTreeClassifier(random_state=12345,
max_depth=depth,
criterion=criterion,
splitter=splitter,
min_samples_split=min_samples_split,
min_samples_leaf= min_samples_leaf,
min_weight_fraction_leaf=min_weight_fraction_leaf,
max_features=max_features)
clf.fit(features_train, target_train)
return clf.score(features_valid, target_valid)
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=1000, show_progress_bar=True)
[I 2023-07-30 14:33:16,435] A new study created in memory with name: no-name-28db142b-1623-40bb-90cc-7f82c45a3d14
Проиллюстрируем результаты экспериментов.
fig = optuna.visualization.plot_optimization_history(study)
fig.show()
По графику можем увидеть, что благодаря перебору множества параметров значение accuracy поэтапно росло. Изнчальное значение в удалось улучшить до 0.792, что является лучшим результатом в данном эксперименте.
Посомтрим, какие гиперпараметры больше всего повлияли на полученный результат.
plot_param_importances(study)
plt.title('Важность гиперпараметров')
plt.xlabel('Уровень важности')
plt.ylabel('Гиперпараметр')
plt.show()
Можно увидеть, что набильший вклад оказали гиперпараметры min_samples_leaf и min_weight_fraction_leaf. Остальные гиперпараметры менее важны для целевой метрики.
Выведем полученные значения гиперпараметров наилучшей модели дерева решений, и посомтрим значение accuracy для данной модели.
display(study.best_params)
study.best_value
{'max_depth': 5,
'criterion': 'entropy',
'splitter': 'best',
'min_samples_split': 10,
'min_samples_leaf': 0.0015342677093210947,
'min_weight_fraction_leaf': 0.00017400428313582225,
'max_features': 'log2'}
0.7916018662519441
Повторно обучим модель с полученными параметрами.
model_tree = DecisionTreeClassifier(random_state=12345,
max_depth=5,
criterion='entropy',
splitter='best',
min_samples_split=10,
min_samples_leaf= 0.0015342677093210947,
min_weight_fraction_leaf=0.00017400428313582225,
max_features='log2')
model_tree.fit(features_train, target_train)
predictions = model_tree.predict(features_valid)
accuracy = accuracy_score(target_valid, predictions)
accuracy
0.7916018662519441
Итоговое значение accuracy для решающего дерева равняется 0.792.
Протестируем модель случайного леса. Построим модель, не меняя геперпараметры.
model_forest = RandomForestClassifier(random_state=12345)
model_forest.fit(features_train, target_train)
predictions = model_forest.predict(features_valid)
accuracy = accuracy_score(target_valid, predictions)
accuracy
0.7853810264385692
Уже имеем значение accuracy равное 0.785. Это лучше, чем начальная модель решающего дерева. Посмотрим, какого значения мы сможем добиться, меняя гиперпараметры.
Добавим перебор некоторых гиперпараметров модели для улучшения метрики accuracy.
%%capture optuna_output
def objective_forest(trial):
est = trial.suggest_int('n_estimators', 10, 51)
depth = trial.suggest_int('max_depth', 1, 10)
criterion = trial.suggest_categorical('criterion', ['gini', 'entropy'])
min_samples_split = trial.suggest_int('min_samples_split', 2, 10)
max_features = trial.suggest_categorical('max_features', ['sqrt', 'auto', 'log2'])
clf = RandomForestClassifier(random_state=12345,
n_estimators = est,
max_depth=depth,
criterion=criterion,
min_samples_split=min_samples_split,
max_features=max_features)
clf.fit(features_train, target_train)
return clf.score(features_valid, target_valid)
study_forest = optuna.create_study(direction='maximize')
study_forest.optimize(objective_forest, n_trials=200, show_progress_bar=True)
[I 2023-07-30 14:34:49,072] A new study created in memory with name: no-name-b9c06276-ed3d-4716-ad60-4640d067d463
Визуализируем результаты экспериментов.
fig = optuna.visualization.plot_optimization_history(study_forest)
fig.show()
По графику можем увидеть, что благодаря перебору множества параметров значение accuracy поэтапно росло. Изнчальное значение в удалось улучшить до 0.810, что является лучшим результатом в данном эксперименте.
Посомтрим, какие гиперпараметры больше всего повлияли на полученный результат.
plot_param_importances(study_forest)
plt.title('Важность гиперпараметров')
plt.xlabel('Уровень важности')
plt.ylabel('Гиперпараметр')
plt.show()
Можно увидеть, что набильший вклад оказал гиперпараметр max_depth. Остальные гиперпараметры менее важны для целевой метрики.
Выведем полученные значения гиперпараметров наилучшей модели случайного леса, и посмотрим значение accuracy для данной модели.
display(study_forest.best_params)
study_forest.best_value
{'n_estimators': 39,
'max_depth': 7,
'criterion': 'gini',
'min_samples_split': 3,
'max_features': 'log2'}
0.8102643856920684
Повторно обучим модель с полученными параметрами.
model_forest = RandomForestClassifier(random_state=12345,
n_estimators=39,
max_depth=7,
criterion='gini',
min_samples_split=3,
max_features='log2')
model_forest.fit(features_train, target_train)
predictions = model_forest.predict(features_valid)
accuracy = accuracy_score(target_valid, predictions)
accuracy
0.8102643856920684
Итоговое значение accuracy для решающего дерева равняется 0.810.
Протестируем модель логистической регрессии.
model_lr = LogisticRegression(random_state=12345, solver='lbfgs', max_iter=1000)
model_lr.fit(features_train, target_train)
predictions = model_lr.predict(features_valid)
accuracy = accuracy_score(target_valid, predictions)
accuracy
0.7107309486780715
Логистическая регрессия показала не самое лучшее значение accuracy — 0.71.
Добавим перебор некоторых гиперпараметров модели для улучшения метрики accuracy.
%%capture optuna_output
def objective_regression(trial):
solver = trial.suggest_categorical('solve', ['liblinear', 'newton-cg', 'lbfgs', 'sag', 'saga'])
max_iter = trial.suggest_int('max_iter', 100, 1000, 100)
class_weight = trial.suggest_categorical('class_weight', ['balanced', None])
fit_intercept = trial.suggest_categorical('fit_intercept', [True, False])
reg = LogisticRegression(random_state=12345,
solver=solver,
max_iter=max_iter,
class_weight=class_weight,
fit_intercept= fit_intercept)
reg.fit(features_train, target_train)
return reg.score(features_valid, target_valid)
study_regression = optuna.create_study(direction='maximize')
study_regression.optimize(objective_regression, n_trials=200, show_progress_bar=True)
[I 2023-07-30 14:35:20,263] A new study created in memory with name: no-name-439a52f2-a492-45a4-afb9-a43cf9085239
Визуализируем результаты экспериментов.
fig = optuna.visualization.plot_optimization_history(study_regression)
fig.show()
По графику можем увидеть, что благодаря перебору множества параметров значение accuracy поэтапно росло. Изнчальное значение удалось улучшить до 0.756, что является лучшим результатом в данном эксперименте.
Посомтрим, какие гиперпараметры больше всего повлияли на полученный результат.
plot_param_importances(study_regression)
plt.title('Важность гиперпараметров')
plt.xlabel('Уровень важности')
plt.ylabel('Гиперпараметр')
plt.show()
Можно увидеть, что набильший вклад оказал гиперпараметр class_weight. Остальные гиперпараметры менее важны для целевой метрики.
Выведем полученные значения гиперпараметров наилучшей модели логистической регрессии, и посмотрим значение accuracy для данной модели.
display(study_regression.best_params)
study_regression.best_value
{'solve': 'newton-cg',
'max_iter': 700,
'class_weight': None,
'fit_intercept': True}
0.7558320373250389
Повторно обучим модель с полученными параметрами.
model_lr = LogisticRegression(random_state=12345,
solver='newton-cg',
max_iter=700,
fit_intercept=True,
class_weight=None)
model_lr.fit(features_train, target_train)
predictions = model_lr.predict(features_valid)
accuracy = accuracy_score(target_valid, predictions)
accuracy
0.7558320373250389
Итоговое значение accuracy для решающего дерева равняется 0.756.
Вывод
Мы протестировали три модели машинного обучения: логистическая регрессия, дерево решений и случайный лес. Анализируя метрику качества accuracy, можно сказать, что наилучшие результаты дала модель случайного леса. Подбирая гиперпараметры, удалось установить, что при лучший вариант данной модели выдаёт точные предсказания в более чем 80% случаев на валидационной выборке. Можно переходить к проверке данной модели на тестовой выборке.
Проверим качество случайного леса на тестовой выборке, задав ранее установленные гиперпараметры.
model_forest = RandomForestClassifier(random_state=12345,
n_estimators=39,
max_depth=7,
criterion='gini',
min_samples_split=3,
max_features='log2')
model_forest.fit(features_train, target_train)
predictions = model_forest.predict(features_test)
accuracy = accuracy_score(target_test, predictions)
accuracy
0.7978227060653188
На тестовой выборке данная модель показывает результаты довольно близкие к тем, что были и на валидационной. Значение accuracy на валидационной выборке равнялось 0.810, а на тестовой получилось 0.798, что удовлетворяет изначальному требованию к данной метрике.
Для того, чтобы проверить нашу полученную модель на адекватность, определим, какое качество будет иметь модель, которая просто присваивает всем объектам наиболее встречающийся в выборке класс. Для этого вопользуемся DummyClassifier.
model_dummy = DummyClassifier(strategy='most_frequent', random_state=12345)
model_dummy.fit(features_train, target_train)
predictions = model_dummy.predict(features_test)
accuracy = accuracy_score(target_test, predictions)
accuracy
0.6842923794712286
Для простейшей модели модели метрика accuracy имеет значение 0.684.
Вывод
Таким образом, если модель не анализирует параметры, а просто присваивает объекту самый популярный класс, то она в итоге угадает в 68% случаев. Модель, которую мы получили, обучая случайный лес, делает успешный прогноз в практически 80% случаев, что делает её более качественной, чем банальное константное предсказание наиболее часто встречаемым классом.
В ходе исследования была проведена работа о поведении клиентов сотового оператора.
Исследуя обработанные данные, мы: